//{==================================================================================================
//! @file "main.cpp"
//! @date 2014-02-25
//! @mainpage Program to multiply numbers using long arithmetic
//! @version 1.0
//}==================================================================================================

//***************************************************************************************************
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
//***************************************************************************************************

//***************************************************************************************************
#define USE_USER_CONSOLE_INTERFACE
#define DEBUG
//---------------------------------------------------------------------------------------------------
#ifdef USE_USER_CONSOLE_INTERFACE
    #define OUT         printf
#else
    #define OUT if (0)  printf
#endif

#ifdef DEBUG
    #define DDOUT         printf

    #define INDENT( ACTION )                        \
        for (unsigned i = 0; i < left_indent; i++)  \
            putchar(' ');                           \
        ACTION;
#else
    #define DDDOUT if (0)  printf
    #define INDENT( ACTION )
#endif
//***************************************************************************************************


//***************************************************************************************************
const char      RADIX = 10;
const unsigned  NUMBER_MAX_LEN = 1000;
//---------------------------------------------------------------------------------------------------
unsigned        left_indent = 0;
//***************************************************************************************************


//***************************************************************************************************
typedef struct
{
    unsigned used;
    char    *mass;
} ULong;
//***************************************************************************************************


//***************************************************************************************************
// FUNCTIONS
//---------------------------------------------------------------------------------------------------
void            UL_constructor          (ULong **L);
void            UL_destructor           (ULong *L);
inline short    UL_isok                 (const ULong *L);
void            UL_invert               (ULong *L);
void            UL_build_using_source   (ULong **L, FILE *source, char sequence_end_symbol);
void            UL_add                  (const ULong *L1, const ULong *L2, ULong *L);
void            UL_shift_left           (ULong *L, const unsigned offset_size);
void            UL_mul_digit            (const ULong *L1, const unsigned dig, ULong *L);
void            UL_mul                  (const ULong *L1, const ULong *L2, ULong *L);
void            UL_inverted_print       (ULong *L);
//***************************************************************************************************


//***************************************************************************************************
int main ()
{
    OUT ("Please, enter your two long numbers\n"
         "The maximum length of the number is determined by a constant at compile time\n"
         "For this assembly it's NUMBER_MAX_LEN = %u\n"
         "//press Enter key to end the input sequence//\n\n",
         NUMBER_MAX_LEN);

    ULong *a_number = NULL,
          *b_number = NULL;

    OUT ("ENTER A NUMBER\n");
    UL_build_using_source (&a_number, stdin, '\n');
    assert (UL_isok(a_number));

    OUT ("ENTER B NUMBER\n");
    UL_build_using_source (&b_number, stdin, '\n');
    assert (UL_isok(b_number));

    ULong *ab_number = NULL;
    UL_constructor (&ab_number);
    assert (UL_isok(ab_number));

    UL_mul (a_number, b_number, ab_number);

    OUT ("THE RESULT OF MULTIPLICATION IS\n");
    UL_inverted_print (ab_number);

    UL_destructor (a_number);
    UL_destructor (b_number);
    UL_destructor (ab_number);

    return 0;
}
//***************************************************************************************************


inline short UL_isok (const ULong *L)
{
    INDENT (DDOUT ("# called UL_isok[%p]\n", L));
    assert (L);
    return (L->mass && L->used < NUMBER_MAX_LEN);
}

void UL_constructor (ULong **L)
{
    INDENT (DDOUT ("#  called UL_constructor\n"));
    left_indent++;

    *L = (ULong *) calloc (1, sizeof(ULong));
    assert (*L);

    (*L)->mass = (char*)calloc(NUMBER_MAX_LEN, sizeof (char));
    assert ((*L)->mass);

    left_indent--;
    INDENT (DDOUT ("#  leave UL_constructor: OK.\n"));
}


void UL_invert (ULong *L)
{
    INDENT (DDOUT ("# called UL_Invert[%p]\n", L));
    left_indent++;

    assert (UL_isok (L));

    for (unsigned i = 0; i < L->used / 2; i++)
    {
        register unsigned temp = L->mass[i];
        L->mass[i] = L->mass[L->used-i-1];
        L->mass[L->used-i-1] = (char)temp;
    }

    assert (UL_isok (L));

    left_indent--;
    INDENT (DDOUT ("# leave UL_Invert: OK.\n"));
}

void UL_build_using_source (ULong **L, FILE *source, char sequence_end_symbol)
{
    INDENT (DDOUT ("# called UL_build_using_source([%p], [%p], %c)\n", L, source, sequence_end_symbol));
    left_indent++;

    UL_constructor (L);
    UL_isok (*L);

    unsigned i = 0;
    char buffer = 0;

    while (buffer != sequence_end_symbol)
    {
        assert (i <= NUMBER_MAX_LEN);

        buffer = (char)getc(source);
        (*L)->mass[i++] = (char)(buffer - '0');
    }

    (*L)->used = i-1;
    (*L)->mass[i-1] = 0;

    UL_invert (*L);

    left_indent--;
    INDENT (DDOUT ("# leave UL_build_using_source: OK.\n"));
}

void UL_destructor (ULong *L)
{
    INDENT (DDOUT ("# called UL_destructor[%p]\n", L));
    left_indent++;

    assert (L);

    if (L->mass) free (L->mass);
    L->mass = NULL;
    L->used = 666; // STD DESTRUCTOR VALUE
    free (L);

    left_indent--;
    INDENT (DDOUT ("# leave UL_destructor: OK.\n"));

}


//void UL_dump (ULong *L)
//{
//    DDOUT ("# called UL_dump[%p]\n"
//         "# this ULong is %s\n"
//         "# ULong[%p]->used = %u\n"
//         "# It has own data array ULong[%p]->mass = [%p] :\n\n",
//         L, UL_isok(L) ? "OK" : "BAD!", L, L->used, L, L->mass);
//
//    register unsigned i = 0;
//
//    for (; i < L->used; i++)
//        DDOUT ("[%u] ", L->mass[i]);
//
//    for (; i < NUMBER_MAX_LEN; i++)
//        DDOUT ("|%u| ", L->mass[i]);
//
//    DDOUT ("\n"
//         "# END DUMP\n");
//
//}

void UL_inverted_print (ULong *L)
{
    INDENT (DDOUT ("# called UL_inverted_print[%p]\n", L));
    left_indent++;

    assert (UL_isok (L));

    for (unsigned i = L->used; i > 0; i--)
        printf("%u", L->mass[i-1]);
    printf ("\n");

    assert (UL_isok (L));

    left_indent--;
    INDENT (DDOUT ("# leave UL_inverted_print: OK.\n"));
}

//int UL_cmp (const ULong *L1, const ULong *L2)
//{
//    INDENT (DDOUT ("# called UL_Invert([%p], [%p])\n", L1, L2));
//    left_indent++;
//
//    assert (UL_isok (L1));
//    assert (UL_isok (L2));
//
//    left_indent--;
//
//    if (L1->used > L2->used) return  1;
//    if (L1->used < L2->used) return -1;
//
//    register unsigned i = L1->used;
//
//    while (i >= 1 && L1->mass[i] == L2->mass[i]) i--;
//
//    if (i = 0)
//        return 0;
//    else
//        return L1->mass[i]-L2->mass[i];
//}

void UL_add (const ULong *L1, const ULong *L2, ULong *L)
{
    INDENT (DDOUT ("# called UL_add([%p], [%p], [%p])\n", L1, L2, L));
    left_indent++;

    assert (UL_isok (L1));
    assert (UL_isok (L2));
    assert (UL_isok (L));

    register unsigned temp   = 0,
                      i      = 0;

    while (i <= L1->used && i <= L2->used)
    {
        temp += L1->mass[i] + L2->mass[i];
        L->mass[i++] = (char)(temp % RADIX);
        temp /= RADIX;
    }

    #define MAKE_LR_STD_USING( largest_ul )     \
    {                                           \
        L->used = largest_ul->used;             \
        while (i <= largest_ul->used)           \
        {                                       \
            temp += largest_ul->mass[i];        \
            L->mass[i++] = (char)(temp % RADIX);\
            temp /= RADIX;                      \
        }                                       \
    }

    if (L1->used >= L2->used)
        MAKE_LR_STD_USING (L1)
    else
        MAKE_LR_STD_USING (L2)

    #undef MAKE_LR_STD_USING

    if (temp > 0)
    {
        assert (L->used+1 <= NUMBER_MAX_LEN);
        L->used++;
        L->mass[L->used] = (char)temp;
    }

    assert (UL_isok (L));

    left_indent--;
    INDENT (DDOUT ("# leave UL_add: OK.\n"));
}

void UL_mul_digit (const ULong *L1, const unsigned dig, ULong *L)
{
    INDENT (DDOUT ("# called UL_mul([%p], %u, [%p])\n", L1, dig, L));
    left_indent++;

    assert (UL_isok (L1));
    assert (UL_isok (L));

    L->used = L1->used;

    register unsigned temp   = 0,
                      i      = 0;

    for (; i < L1->used; i++)
    {
        L->mass[i] = L1->mass[i];
        L->mass[i] *= (char)dig;
        L->mass[i] += (char)temp;
        temp = L->mass[i] / RADIX;
        L->mass[i] %= RADIX;
    }

    L->mass[i] = (char)temp;

    if (L->mass[i])
    {
        assert (L->used+1 <= NUMBER_MAX_LEN);
        L->used++;
    }

    assert (UL_isok (L));

    left_indent--;
    INDENT (DDOUT ("# leave UL_add: OK.\n"));
}

void UL_shift_left (ULong *L, const unsigned offset_size)
{
    INDENT (DDOUT ("# called UL_shift_left([%p])\n", L));
    left_indent++;

    assert (UL_isok (L));
    assert (L->used+offset_size <= NUMBER_MAX_LEN);

    L->used += offset_size;

    for (register unsigned i = L->used-1; i > offset_size-1; i--)
        L->mass[i] = L->mass[i-offset_size];

    for (register unsigned i = 0; i < offset_size; i++)
        L->mass[i] = 0;

    assert (UL_isok (L));

    left_indent--;
    INDENT (DDOUT ("# leave UL_shift_left: OK.\n"));
}

void UL_mul (const ULong *L1, const ULong *L2, ULong *L)
{
    INDENT (DDOUT ("# called UL_mul([%p], [%p], [%p])\n", L1, L2, L));
    left_indent++;

    assert (UL_isok (L1));
    assert (UL_isok (L2));
    assert (UL_isok (L));

    ULong *temp = NULL;
    UL_constructor (&temp);

    assert (UL_isok (temp));

    L->used = 0;
    for (register unsigned i = 0; i < L2->used; i++)
    {
        UL_mul_digit (L1, L2->mass[i], temp);
        UL_shift_left (temp, i);
        UL_add (L, temp, L);
    }

    assert (UL_isok (L));

    UL_destructor (temp);

    left_indent--;
    INDENT (DDOUT ("# leave UL_mul: OK.\n"));

}


